/**
@C++ Standard: 11
@Copyright:Copyright (c) 2019 Harold Hu. All rights reserved.
@Author: Created by Harold Hu with CLion on 2019/1/15 15:15.
@File Name: ScopeGuard.h
@Description Define a class that can do clean work when leaving it's scope if an exception happened
*/
#ifndef HAROLD_SCOPEGUARD_H
#define HAROLD_SCOPEGUARD_H

#include <utility>
#include <functional>

/**
 * @Description Can do clean work when leaving it's scope if an exception happened
 * @Attention Use ScopeGuard by its wrapper function: createGuard, avoid using ScopeGuard directly
 * @tparam NoRetCallable: type of callable object (should return void) to do clean work(Lambda, function pointer...)
 * @tparam Args: type of parameters of callable object to do clean work
 * @Copyable No
 * @Moveable Yes
 */
template<typename NoRetCallable, typename ...Args>
class ScopeGuard
{
	using funcType = std::function<void()>;
public:
	/**
	 * @Description constructor to store callable obj and it's Args
	 * @param fun: callable object (should return void) to do clean work
	 * @param arg: parameters of callable object to do clean work
	 */
	ScopeGuard(NoRetCallable&& fun, Args&& ...arg) : _safeToLeave(false), _callableObj(
			std::bind(std::forward<NoRetCallable>(fun), std::forward<Args>(arg)...))
	{
	}
	
	/**
	 * @Description ScopeGuard copy assignment constructor
	 * @param rhs: source to copy
	 */
	ScopeGuard& operator=(const ScopeGuard& rhs)
	{
		if (this != &rhs)
		{
			_safeToLeave = rhs._safeToLeave;
			rhs._safeToLeave = true;
		}
		return *this;
	}
	
	/**
	 * @Description ScopeGuard move assignment constructor
	 * @param rhs: source to move
	 */
	ScopeGuard& operator=(ScopeGuard&& rhs) noexcept
	{
		if (this != &rhs)
		{
			_safeToLeave = rhs._safeToLeave;
			rhs._safeToLeave = true;
		}
		return *this;
	}
	
	/**
	 * @Description ScopeGuard copy constructor
	 * @param rhs: source to copy
	 */
	ScopeGuard(const ScopeGuard& rhs)
	{
		if (this != &rhs)
		{
			_safeToLeave = rhs._safeToLeave;
			rhs._safeToLeave = true;
		}
	}
	
	/**
	 * @Description ScopeGuard move constructor
	 * @param rhs: source to move
	 */
	ScopeGuard(ScopeGuard&& rhs) noexcept
	{
		if (this != &rhs)
		{
			_safeToLeave = rhs._safeToLeave;
			rhs._safeToLeave = true;
		}
	}
	
	
	/**
	 * @Description don't need to do clean work
	 * @Attention No other codes except this type functions (xxx.beSafe()) should appear later in it's living scope
	 */
	void beSafe() const noexcept
	{
		_safeToLeave = true;
	}
	
	/**
	 * @Description destructor (RAII tech to do clean work)
	 * @Attention make sure _callableObj() won't throw exception would be better
	 */
	~ScopeGuard()
	{
		if (!_safeToLeave)
		{
			_callableObj();
		}
	}

protected:
	funcType _callableObj;///<	callable object to do clean work
	mutable bool _safeToLeave;///<	flag: safe to avoid do clean work
};

/**
 * @Description use parameter derivation to create an ScopeGuard obj avoid too many template parameters visible declaration
 * @tparam NoRetCallable: type of callable object (should return void) type
 * @tparam Args: type of parameters of callable object types
 * @param fun: callable object (Lambda, function pointer...)
 * @param arg: parameters of callable (for class no-static member function pointer, an obj (pointer) is needed after fun)
 * @Attention arg should be wrapped by std::ref() or std::cref() if you do want to pass args by reference
 * @return ScopeGuard: an ScopeGuard obj
 */
template<typename NoRetCallable, typename ...Args>
inline ScopeGuard<NoRetCallable, Args...> createGuard(NoRetCallable&& fun, Args&& ...arg)
{
	return ScopeGuard<NoRetCallable, Args...>(std::forward<NoRetCallable>(fun),
											  std::forward<Args>(arg)...);/* may do RVO */
}


#endif //HAROLD_SCOPEGUARD_H
